#include <WiFi.h>
#include <PubSubClient.h>
#include <Arduino_JSON.h>
#include <BluetoothSerial.h>
#include <Preferences.h>

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
//////////////////////低功耗蓝牙
// See the following for generating UUIDs: https://www.uuidgenerator.net/
#define SERVICE_UUID "00001101-0000-1000-8000-00805F9B34FB"  // UART service UUID
#define CHARACTERISTIC_UUID_RX "12a59e0a-17cc-11ec-9621-0242ac130002"
#define CHARACTERISTIC_UUID_TX "12a5a148-17cc-11ec-9621-0242ac130002"
BLEServer *pServer = NULL;                    //BLEServer指针 pServer
BLECharacteristic *pTxCharacteristic = NULL;  //BLECharacteristic指针 pTxCharacteristic

//////////////////////经典蓝牙
BluetoothSerial SerialBT;

//////////////////////记录本地数据
Preferences prefs;  //

//////////////////////MQTT
#define mqttServer "iot.lciot.net"  //服务器地址
//#define mqttServer "120.24.193.205"
#define mqttPort 1883                            //服务器端口
#define user "admin"                               //用户名
#define pass "123456"                                  //密码
String clientId = "ESP32-" + WiFi.macAddress();  //客户端名字，后面的string函数是为了加随机id,防止重复

String pubTopic = "v1/devices/me/rpc/response";  //发布消息的主题v1/devices/me/rpc/response/1
#define subTopic "v1/devices/me/rpc/request/+"   //订阅消息的主题v1/devices/me/rpc/request/1

WiFiClient espClient;            // 定义wifiClient实例
PubSubClient client(espClient);  // 定义PubSubClient的实例

void setup() {
  Serial.begin(115200);
  xTaskCreatePinnedToCore(usartCmd, "usartCmd", 2048, NULL, 2, NULL, tskNO_AFFINITY);
  blufi();
  //configureNetwork();
  client.setServer(mqttServer, mqttPort);  //MQTT服务器连接函数（服务器IP，端口号）
  client.setCallback(mqttCallback);        //设定回调方式，当ESP32收到订阅消息时会调用此方法
  client.setKeepAlive(60);
  pinMode(2, OUTPUT);
}

void loop() {
  //////////////////////////////////////////////////////////////////////////////////MQTT
  if (!client.connected()) {
    client.connect(clientId.c_str(), user, pass);  //名字连接服务器
    client.subscribe(subTopic);                    //连接MQTT服务器后订阅主题
    Serial.println("mqtt connect ok");
  }
  client.loop();  //回旋接收函数  等待服务器返回的数据

  //usartRead();  //读取串口的消息并处理
}
class MyServerCallbacks : public BLEServerCallbacks {
  void onConnect(BLEServer *pServer){
    //deviceConnected = true;
  };

  void onDisconnect(BLEServer *pServer) {
    //deviceConnected = false;
  }
};

class MyCallbacks : public BLECharacteristicCallbacks {
  void onWrite(BLECharacteristic *pCharacteristic) {
    std::string rxValueStd = pCharacteristic->getValue();
    String rxValue = String(rxValueStd.c_str()); //接收信息

    if (rxValue.length() > 0) {  //向串口输出收到的值
      Serial.print("RX: ");
      for (int i = 0; i < rxValue.length(); i++)
        Serial.print(rxValue[i]);
      Serial.println();
    }

    JSONVar myObject = JSON.parse(rxValue);      //通过josn解析返回结果
    if (JSON.typeof(myObject) == "undefined") {  //如果解析错误
      Serial.println("Parsing input failed!");
      return;
    }

    if ((const char *)myObject["wifi"]["ssid"] == null)
      return;
    if ((const char *)myObject["wifi"]["pass"] == null)
      return;

    String ssid = (const char *)myObject["wifi"]["ssid"];
    String password = (const char *)myObject["wifi"]["pass"];
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);
    if (ConnectWifi()) {
      Serial.println("Connect failed");
      SerialBT.println("Connect failed");
      SerialBT.println("Please Try Again");
      pTxCharacteristic->setValue((uint8_t *)"Connect failed Please Try Again", 31);
      pTxCharacteristic->notify();
      return;
    }
    Serial.println("Connect success");
    const char *successMessage = "{\"status\":\"ok\"}";
    pTxCharacteristic->setValue((uint8_t *)successMessage, strlen(successMessage));
    pTxCharacteristic->notify();
    prefs.end();
  }
};

void blufi() {
  prefs.begin("esp32");  // 打开命名空间mynamespace
  String ssid = prefs.getString("ssid", " ");
  String password = prefs.getString("password", " ");
  Serial.printf("mqtt clientId:%s \r\n", clientId.c_str());
  Serial.printf("SSID:%s, PSK:%s  \r\n", ssid.c_str(), password.c_str());

  //////////////////////////////////////////用存储的wifi信息连接wifi
  WiFi.begin(ssid, password);
  if (!ConnectWifi())
    return;
  Serial.println("WIFI CONNECT failed");

  Serial.println("BLE CONNECT START");

  // 创建一个 BLE 设备
  std::string clientIdStd = clientId.c_str();
  BLEDevice::init(clientIdStd);//在这里面是ble的名称
  BLEDevice::setMTU(100);     //在这里面是ble的名称
  //Serial.println(BLEDevice::getMTU());

  // 创建一个 BLE 服务
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());  //设置回调
  BLEService *pService = pServer->createService(SERVICE_UUID);

  // 创建一个 BLE 特征
  pTxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_NOTIFY);
  pTxCharacteristic->addDescriptor(new BLE2902());
  BLECharacteristic *pRxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE);
  pRxCharacteristic->setCallbacks(new MyCallbacks());  //设置回调

  pService->start();                   // 开始服务
  pServer->getAdvertising()->start();  // 开始广播
  Serial.println("Waiting Connect BLE...");

  while (WiFi.status() != WL_CONNECTED) {
    delay(5000);
    pTxCharacteristic->setValue((uint8_t *)"Waiting Connect\n", 17);
    pTxCharacteristic->notify();
  }
}

void configureNetwork() {
  prefs.begin("esp32");  // 打开命名空间mynamespace
  String ssid = prefs.getString("ssid", " ");
  String password = prefs.getString("password", " ");
  Serial.printf("mqtt clientId:%s \r\n", clientId.c_str());
  Serial.printf("SSID:%s, PSK:%s  \r\n", ssid.c_str(), password.c_str());

  //////////////////////////////////////////用存储的wifi信息连接wifi
  WiFi.begin(ssid, password);
  if (!ConnectWifi())
    return;
  Serial.println("WIFI CONNECT failed");

  //////////////////////////////////////////SmartConfig配网
  digitalWrite(2, HIGH);
  WiFi.mode(WIFI_AP_STA);
  WiFi.beginSmartConfig();

  //等待来自手机的SmartConfig数据包
  Serial.println("Waiting for SmartConfig.");
  for (int i = 0; i < 60; i++) {
    Serial.print(".");
    delay(500);
    if (!WiFi.smartConfigDone())  //如果未接收到配网信息，跳过后续内容，继续循环
      continue;
    if (ConnectWifi())  //如果连接失败，跳过SmartConfig，进行蓝牙配网
      break;
    digitalWrite(2, LOW);
    return;  //连接成功跳出
  }
  digitalWrite(2, LOW);
  //////////////////////////////////////////蓝牙配网
  Serial.println("Waiting for ble");
  while (true) {
    SerialBT.begin(clientId);  // 如果没有参数传入则默认是蓝牙名称是: "ESP32"
    char buff[100];
    while (true) {
      delay(1);
      if (SerialBT.available() <= 0)
        continue;
      if (SerialBT.peek() != '{')
        continue;
      delay(10);

      int len = 0;
      while (SerialBT.available() > 0)
        buff[len++] = SerialBT.read();
      buff[len] = 0;
      break;
    }
    Serial.println(buff);

    JSONVar myObject = JSON.parse(buff);         //通过josn解析返回结果
    if (JSON.typeof(myObject) == "undefined") {  //如果解析错误
      Serial.println("Parsing input failed!");
      continue;
    }

    if ((const char *)myObject["ssid"] == null)
      continue;
    if ((const char *)myObject["pass"] == null)
      continue;

    ssid = (const char *)myObject["ssid"];
    password = (const char *)myObject["pass"];
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);
    if (ConnectWifi()) {
      Serial.println("Connect failed");
      SerialBT.println("Connect failed");
      SerialBT.println("Please Try Again");
      continue;
    }
    Serial.println("Connect success");
    SerialBT.println("Connect success");
    SerialBT.disconnect();
    SerialBT.end();
    prefs.end();
    return;
  }
}
int ConnectWifi() {
  WiFi.mode(WIFI_STA);
  for (int i = 0; i < 20; i++) {
    Serial.print('.');
    delay(200);
    if (WiFi.status() != WL_CONNECTED)
      continue;
    Serial.println(WiFi.localIP());
    if (strcmp(WiFi.SSID().c_str(), prefs.getString("ssid", " ").c_str()) || strcmp(WiFi.psk().c_str(), prefs.getString("password", " ").c_str())) {
      prefs.putString("ssid", WiFi.SSID());
      prefs.putString("password", WiFi.psk());
      Serial.println("ssid and password save success");
    }
    return 0;
  }
  return -1;
}
void usartCmd(void *pvParameters) {
  char text[100];
  while (true) {
    delay(1);
    if (Serial.available() <= 1)
      continue;
    if (Serial.read() != 'A')
      continue;
    if (Serial.read() != 'T')
      continue;
    delay(2);

    int len = 0;
    while (Serial.available() > 0) {
      text[len++] = Serial.read();
    }
    text[len] = 0;
    // if(len<2){
    //   Serial.println("OK");
    //   continue;
    // }
    if (strcmp(text, "") == 0) {
      Serial.println("OK");
    } else if (strcmp(text, "+ResetWifi") == 0) {
      Preferences prefsUsart;
      prefsUsart.begin("esp32");  // 打开命名空间mynamespace
      prefsUsart.putString("ssid", " ");
      prefsUsart.putString("password", " ");
      prefsUsart.end();
      Serial.println("ResetWifi OK");
      ESP.restart();
    } else if (strcmp(text, "+Restart") == 0) {
      Serial.println("Restart OK");
      ESP.restart();
      continue;
    } else {
      Serial.println("CMD ERROR");
    }
  }
}

void usartRead() {
  if (Serial.available() <= 0)
    return;
  if (Serial.peek() != '{')
    return;
  delay(5);
  char text[100];
  int len = 0;
  while (Serial.available() > 0) {
    text[len++] = Serial.read();
  }
  text[len] = 0;
  client.publish(pubTopic.c_str(), text);
}

void mqttCallback(char *topic, byte *payload, unsigned int length) {
  char *ret = strrchr(topic, '/');
  if (ret == null)
    return;
  String pubTopicTemp = pubTopic + ret;
  Serial.print("subTopic:");
  Serial.println(pubTopicTemp);

  Serial.print("receive:");
  for (int i = 0; i < length; i++)  //使用循环打印接收到的信息
  {
    Serial.print((char)payload[i]);
  }
  Serial.println();

  JSONVar myObject = JSON.parse((char *)payload);  //通过josn解析返回结果
  if (JSON.typeof(myObject) == "undefined") {      //如果解析错误
    Serial.println("Parsing input failed!");
    return;
  }

  if ((const char *)myObject["method"] == null)
    return;

  if (strcmp((const char *)myObject["method"], "setValue") == 0) {
    if ((bool)myObject["params"]) {
      digitalWrite(2, HIGH);
      Serial.println("device:ON");
    } else {
      digitalWrite(2, LOW);
      Serial.println("device:OFF");
    }
  } else if (strcmp((const char *)myObject["method"], "getValue") == 0) {
    if (digitalRead(2)) {
      client.publish(pubTopicTemp.c_str(), "{\"state\":true}");
      Serial.println("return:{\"state\":true}");
    } else {
      client.publish(pubTopicTemp.c_str(), "{\"state\":false}");
      Serial.println("return:{\"state\":false}");
    }
  }
}
